/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 31.05.2007
 *
 * Source: $HeadURL$
 * Last changed: $LastChangedDate$
 * 
 * the unrar licence applies to all junrar source and binary distributions 
 * you are not allowed to use this source to re-create the RAR compression algorithm
 * 
 * Here some html entities which can be used for escaping javadoc tags:
 * "&":  "&#038;" or "&amp;"
 * "<":  "&#060;" or "&lt;"
 * ">":  "&#062;" or "&gt;"
 * "@":  "&#064;" 
 */
package ir.mahdi.mzip.rar.unpack.ppm;

import ir.mahdi.mzip.rar.io.Raw;

public class PPMContext extends Pointer {

    public final static int[] ExpEscape =
            {25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2};
    private static final int unionSize = Math.max(FreqData.size, State.size);
    public static final int size = 2 + unionSize + 4; // 12

    // (1==onestate)
    private final FreqData freqData; // -\

    // |-> union
    private final State oneState; // -/
    // Temp fields
    private final State tempState1 = new State(null);
    private final State tempState2 = new State(null);
    private final State tempState3 = new State(null);
    private final State tempState4 = new State(null);
    private final State tempState5 = new State(null);
    private final int[] ps = new int[256];
    // ushort NumStats;
    private int numStats; // determines if feqData or onstate is used
    private int suffix; // pointer ppmcontext
    private PPMContext tempPPMContext = null;

    public PPMContext(byte[] mem) {
        super(mem);
        oneState = new State(mem);
        freqData = new FreqData(mem);
    }

    public PPMContext init(byte[] mem) {
        this.mem = mem;
        pos = 0;
        oneState.init(mem);
        freqData.init(mem);
        return this;
    }

    public FreqData getFreqData() {
        return freqData;
    }

    public void setFreqData(FreqData freqData) {
        this.freqData.setSummFreq(freqData.getSummFreq());
        this.freqData.setStats(freqData.getStats());
    }

    public final int getNumStats() {
        if (mem != null) {
            numStats = Raw.readShortLittleEndian(mem, pos) & 0xffff;
        }
        return numStats;
    }

    public final void setNumStats(int numStats) {
        this.numStats = numStats & 0xffff;
        if (mem != null) {
            Raw.writeShortLittleEndian(mem, pos, (short) numStats);
        }
    }

    public State getOneState() {
        return oneState;
    }

    public void setOneState(StateRef oneState) {
        this.oneState.setValues(oneState);
    }

    public int getSuffix() {
        if (mem != null) {
            suffix = Raw.readIntLittleEndian(mem, pos + 8);
        }
        return suffix;
    }

    public void setSuffix(int suffix) {
        this.suffix = suffix;
        if (mem != null) {
            Raw.writeIntLittleEndian(mem, pos + 8, suffix);
        }
    }

    public void setSuffix(PPMContext suffix) {
        setSuffix(suffix.getAddress());
    }

    @Override
    public void setAddress(int pos) {
        super.setAddress(pos);
        oneState.setAddress(pos + 2);
        freqData.setAddress(pos + 2);
    }

    private PPMContext getTempPPMContext(byte[] mem) {
        if (tempPPMContext == null) {
            tempPPMContext = new PPMContext(null);
        }
        return tempPPMContext.init(mem);
    }

    public int createChild(ModelPPM model, State pStats/* ptr */,
                           StateRef firstState /* ref */) {
        PPMContext pc = getTempPPMContext(model.getSubAlloc().getHeap());
        pc.setAddress(model.getSubAlloc().allocContext());
        if (pc != null) {
            pc.setNumStats(1);
            pc.setOneState(firstState);
            pc.setSuffix(this);
            pStats.setSuccessor(pc);
        }
        return pc.getAddress();
    }

    public void rescale(ModelPPM model) {
        int OldNS = getNumStats(), i = getNumStats() - 1, Adder, EscFreq;
        // STATE* p1, * p;
        State p1 = new State(model.getHeap());
        State p = new State(model.getHeap());
        State temp = new State(model.getHeap());

        for (p.setAddress(model.getFoundState().getAddress());
             p.getAddress() != freqData.getStats();
             p.decAddress()) {
            temp.setAddress(p.getAddress() - State.size);
            State.ppmdSwap(p, temp);
        }
        temp.setAddress(freqData.getStats());
        temp.incFreq(4);
        freqData.incSummFreq(4);
        EscFreq = freqData.getSummFreq() - p.getFreq();
        Adder = (model.getOrderFall() != 0) ? 1 : 0;
        p.setFreq((p.getFreq() + Adder) >>> 1);
        freqData.setSummFreq(p.getFreq());
        do {
            p.incAddress();
            EscFreq -= p.getFreq();
            p.setFreq((p.getFreq() + Adder) >>> 1);
            freqData.incSummFreq(p.getFreq());
            temp.setAddress(p.getAddress() - State.size);
            if (p.getFreq() > temp.getFreq()) {
                p1.setAddress(p.getAddress());
                StateRef tmp = new StateRef();
                tmp.setValues(p1);
                State temp2 = new State(model.getHeap());
                State temp3 = new State(model.getHeap());
                do {
                    // p1[0]=p1[-1];
                    temp2.setAddress(p1.getAddress() - State.size);
                    p1.setValues(temp2);
                    p1.decAddress();
                    temp3.setAddress(p1.getAddress() - State.size);
                } while (p1.getAddress() != freqData.getStats() && tmp.getFreq() > temp3.getFreq());
                p1.setValues(tmp);
            }
        } while (--i != 0);
        if (p.getFreq() == 0) {
            do {
                i++;
                p.decAddress();
            } while (p.getFreq() == 0);
            EscFreq += i;
            setNumStats(getNumStats() - i);
            if (getNumStats() == 1) {
                StateRef tmp = new StateRef();
                temp.setAddress(freqData.getStats());
                tmp.setValues(temp);
                // STATE tmp=*U.Stats;
                do {
                    // tmp.Freq-=(tmp.Freq >> 1)
                    tmp.decFreq(tmp.getFreq() >>> 1);
                    EscFreq >>>= 1;
                } while (EscFreq > 1);
                model.getSubAlloc().freeUnits(freqData.getStats(), (OldNS + 1) >>> 1);
                oneState.setValues(tmp);
                model.getFoundState().setAddress(oneState.getAddress());
                return;
            }
        }
        EscFreq -= EscFreq >>> 1;
        freqData.incSummFreq(EscFreq);
        int n0 = (OldNS + 1) >>> 1, n1 = (getNumStats() + 1) >>> 1;
        if (n0 != n1) {
            freqData.setStats(model.getSubAlloc().shrinkUnits(freqData.getStats(), n0, n1));
        }
        model.getFoundState().setAddress(freqData.getStats());
    }

    private int getArrayIndex(ModelPPM Model, State rs) {
        PPMContext tempSuffix = getTempPPMContext(Model.getSubAlloc().getHeap());
        tempSuffix.setAddress(getSuffix());
        int ret = 0;
        ret += Model.getPrevSuccess();
        ret += Model.getNS2BSIndx()[tempSuffix.getNumStats() - 1];
        ret += Model.getHiBitsFlag() + 2 * Model.getHB2Flag()[rs.getSymbol()];
        ret += ((Model.getRunLength() >>> 26) & 0x20);
        return ret;
    }

    public int getMean(int summ, int shift, int round) {
        return ((summ + (1 << (shift - round))) >>> (shift));
    }

    public void decodeBinSymbol(ModelPPM model) {
        State rs = tempState1.init(model.getHeap());
        rs.setAddress(oneState.getAddress());// State&
        model.setHiBitsFlag(model.getHB2Flag()[model.getFoundState().getSymbol()]);
        int off1 = rs.getFreq() - 1;
        int off2 = getArrayIndex(model, rs);
        int bs = model.getBinSumm()[off1][off2];
        if (model.getCoder().getCurrentShiftCount(ModelPPM.TOT_BITS) < bs) {
            model.getFoundState().setAddress(rs.getAddress());
            rs.incFreq((rs.getFreq() < 128) ? 1 : 0);
            model.getCoder().getSubRange().setLowCount(0);
            model.getCoder().getSubRange().setHighCount(bs);
            bs = ((bs + ModelPPM.INTERVAL - getMean(bs, ModelPPM.PERIOD_BITS, 2)) & 0xffff);
            model.getBinSumm()[off1][off2] = bs;
            model.setPrevSuccess(1);
            model.incRunLength(1);
        } else {
            model.getCoder().getSubRange().setLowCount(bs);
            bs = (bs - getMean(bs, ModelPPM.PERIOD_BITS, 2)) & 0xFFFF;
            model.getBinSumm()[off1][off2] = bs;
            model.getCoder().getSubRange().setHighCount(ModelPPM.BIN_SCALE);
            model.setInitEsc(ExpEscape[bs >>> 10]);
            model.setNumMasked(1);
            model.getCharMask()[rs.getSymbol()] = model.getEscCount();
            model.setPrevSuccess(0);
            model.getFoundState().setAddress(0);
        }
        //int a = 0;//TODO just 4 debugging
    }

//	public static void ppmdSwap(ModelPPM model, StatePtr state1, StatePtr state2)
//	{
//		byte[] bytes = model.getSubAlloc().getHeap();
//		int p1 = state1.getAddress();
//		int p2 = state2.getAddress();
//		
//		for (int i = 0; i < StatePtr.size; i++) {
//			byte temp = bytes[p1+i];
//			bytes[p1+i] = bytes[p2+i];
//			bytes[p2+i] = temp;
//		}
//		state1.setAddress(p1);
//		state2.setAddress(p2);
//	}

    public void update1(ModelPPM model, int p/* ptr */) {
        model.getFoundState().setAddress(p);
        model.getFoundState().incFreq(4);
        freqData.incSummFreq(4);
        State p0 = tempState3.init(model.getHeap());
        State p1 = tempState4.init(model.getHeap());
        p0.setAddress(p);
        p1.setAddress(p - State.size);
        if (p0.getFreq() > p1.getFreq()) {
            State.ppmdSwap(p0, p1);
            model.getFoundState().setAddress(p1.getAddress());
            if (p1.getFreq() > ModelPPM.MAX_FREQ)
                rescale(model);
        }
    }

    public boolean decodeSymbol2(ModelPPM model) {
        long count;
        int hiCnt, i = getNumStats() - model.getNumMasked();
        SEE2Context psee2c = makeEscFreq2(model, i);
        RangeCoder coder = model.getCoder();
        // STATE* ps[256], ** pps=ps, * p=U.Stats-1;
        State p = tempState1.init(model.getHeap());
        State temp = tempState2.init(model.getHeap());
        p.setAddress(freqData.getStats() - State.size);
        int pps = 0;
        hiCnt = 0;

        do {
            do {
                p.incAddress();// p++;
            } while (model.getCharMask()[p.getSymbol()] == model.getEscCount());
            hiCnt += p.getFreq();
            ps[pps++] = p.getAddress();
        } while (--i != 0);
        coder.getSubRange().incScale(hiCnt);
        count = coder.getCurrentCount();
        if (count >= coder.getSubRange().getScale()) {
            return false;
        }
        pps = 0;
        p.setAddress(ps[pps]);
        if (count < hiCnt) {
            hiCnt = 0;
            while ((hiCnt += p.getFreq()) <= count) {
                p.setAddress(ps[++pps]);// p=*++pps;
            }
            coder.getSubRange().setHighCount(hiCnt);
            coder.getSubRange().setLowCount(hiCnt - p.getFreq());
            psee2c.update();
            update2(model, p.getAddress());
        } else {
            coder.getSubRange().setLowCount(hiCnt);
            coder.getSubRange().setHighCount(coder.getSubRange().getScale());
            i = getNumStats() - model.getNumMasked();// ->NumMasked;
            pps--;
            do {
                temp.setAddress(ps[++pps]);// (*++pps)
                model.getCharMask()[temp.getSymbol()] = model.getEscCount();
            } while (--i != 0);
            psee2c.incSumm((int) coder.getSubRange().getScale());
            model.setNumMasked(getNumStats());
        }
        return (true);
    }

    public void update2(ModelPPM model, int p/* state ptr */) {
        State temp = tempState5.init(model.getHeap());
        temp.setAddress(p);
        model.getFoundState().setAddress(p);
        model.getFoundState().incFreq(4);
        freqData.incSummFreq(4);
        if (temp.getFreq() > ModelPPM.MAX_FREQ) {
            rescale(model);
        }
        model.incEscCount(1);
        model.setRunLength(model.getInitRL());
    }

    private SEE2Context makeEscFreq2(ModelPPM model, int Diff) {
        SEE2Context psee2c;
        int numStats = getNumStats();
        if (numStats != 256) {
            PPMContext suff = getTempPPMContext(model.getHeap());
            suff.setAddress(getSuffix());
            int idx1 = model.getNS2Indx()[Diff - 1];
            int idx2 = 0;
            idx2 += (Diff < suff.getNumStats() - numStats) ? 1 : 0;
            idx2 += 2 * ((freqData.getSummFreq() < 11 * numStats) ? 1 : 0);
            idx2 += 4 * ((model.getNumMasked() > Diff) ? 1 : 0);
            idx2 += model.getHiBitsFlag();
            psee2c = model.getSEE2Cont()[idx1][idx2];
            model.getCoder().getSubRange().setScale(psee2c.getMean());
        } else {
            psee2c = model.getDummySEE2Cont();
            model.getCoder().getSubRange().setScale(1);
        }
        return psee2c;
    }

    public boolean decodeSymbol1(ModelPPM model) {

        RangeCoder coder = model.getCoder();
        coder.getSubRange().setScale(freqData.getSummFreq());
        State p = new State(model.getHeap());
        p.setAddress(freqData.getStats());
        int i, HiCnt;
        long count = coder.getCurrentCount();
        if (count >= coder.getSubRange().getScale()) {
            return false;
        }
        if (count < (HiCnt = p.getFreq())) {
            coder.getSubRange().setHighCount(HiCnt);
            model.setPrevSuccess((2 * HiCnt > coder.getSubRange().getScale()) ? 1 : 0);
            model.incRunLength(model.getPrevSuccess());
            HiCnt += 4;
            model.getFoundState().setAddress(p.getAddress());
            model.getFoundState().setFreq(HiCnt);
            freqData.incSummFreq(4);
            if (HiCnt > ModelPPM.MAX_FREQ) {
                rescale(model);
            }
            coder.getSubRange().setLowCount(0);
            return true;
        } else {
            if (model.getFoundState().getAddress() == 0) {
                return (false);
            }
        }
        model.setPrevSuccess(0);
        int numStats = getNumStats();
        i = numStats - 1;
        while ((HiCnt += p.incAddress().getFreq()) <= count) {
            if (--i == 0) {
                model.setHiBitsFlag(model.getHB2Flag()[model.getFoundState().getSymbol()]);
                coder.getSubRange().setLowCount(HiCnt);
                model.getCharMask()[p.getSymbol()] = model.getEscCount();
                model.setNumMasked(numStats);
                i = numStats - 1;
                model.getFoundState().setAddress(0);
                do {
                    model.getCharMask()[p.decAddress().getSymbol()] = model.getEscCount();
                } while (--i != 0);
                coder.getSubRange().setHighCount(coder.getSubRange().getScale());
                return (true);
            }
        }
        coder.getSubRange().setLowCount(HiCnt - p.getFreq());
        coder.getSubRange().setHighCount(HiCnt);
        update1(model, p.getAddress());
        return (true);
    }

    public String toString() {
        StringBuilder buffer = new StringBuilder();
        buffer.append("PPMContext[");
        buffer.append("\n  pos=");
        buffer.append(pos);
        buffer.append("\n  size=");
        buffer.append(size);
        buffer.append("\n  numStats=");
        buffer.append(getNumStats());
        buffer.append("\n  Suffix=");
        buffer.append(getSuffix());
        buffer.append("\n  freqData=");
        buffer.append(freqData);
        buffer.append("\n  oneState=");
        buffer.append(oneState);
        buffer.append("\n]");
        return buffer.toString();
    }

}
